#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sqlite3.h>

#include "main.h"
#include "db.h"
#include "db_sqlite3.h"
#include "utils.h"
#include "xmalloc.h"
#include "string.h"


#ifdef ENABLE_PTHREADS
#include <pthread.h>
pthread_mutex_t db_open_mutex = PTHREAD_MUTEX_INITIALIZER;
#endif

int
db_sqlite3_preinit(void)
{
#ifdef ENABLE_PTHREADS
  int rc;
  if ( (rc = sqlite3_config(SQLITE_CONFIG_MULTITHREAD)) != SQLITE_OK )
  {
    E("Can not config sqlite3 threads: sqlite3_config returned %d.", rc);
    E2("SQLITE3 will not work properly; this means sqlite3 can DAMAGE your db file!");
  } else if (sqlite3_threadsafe())
    {
      D2("SQLITE3 is threadsafe");
    } else {
      E2("SQLITE3 host is not threadsafe, but application is compiled with pthreads, abort.");
      return (DB_ERR_THRD);
    }
#endif

  I("SQLITE3 host version: %s", sqlite3_libversion());
#ifdef SQLITE_VERSION
  if ( strcmp(sqlite3_libversion(),SQLITE_VERSION) != 0 )
  {
    E("Compiled against %s, this may not work.", SQLITE_VERSION);
  }
  assert( strcmp(sqlite3_libversion(),SQLITE_VERSION)==0 );
#endif

  return (sqlite3_initialize());
}

void
db_sqlite3_shutdown(void)
{
  sqlite3_shutdown();
}

int
db_sqlite3_init(const char *path, void **sq3_hndl)
{
  int retval = 0;
  char fulpath[MAXPATHLEN+1];
  sqlite3 *db = NULL;

  assert(sq3_hndl != NULL);
  memset(fulpath, 0, sizeof(fulpath));
  snprintf(fulpath, MAXPATHLEN, "%s/%s"
#ifdef DB_SQ3_SUFFIX
  "." DB_SQ3_SUFFIX
#endif
  , db_get_arg(), path);
#ifdef ENABLE_PTHREADS
  pthread_mutex_lock(&db_open_mutex);
#endif
  retval = sqlite3_open(fulpath, &db);
#ifdef ENABLE_PTHREADS
  pthread_mutex_unlock(&db_open_mutex);
#endif
  if (retval != SQLITE_OK)
  {
    E("Can't open database('%s'): %s", fulpath, sqlite3_errmsg(db));
    sqlite3_close(db);
    db = NULL;
  } else {
#ifdef SQ3_STMT_LEN
    sqlite3_limit(db, SQLITE_LIMIT_SQL_LENGTH, SQ3_STMT_LEN);
#endif
#ifdef SQ3_MAXCOLS
    sqlite3_limit(db, SQLITE_LIMIT_COLUMN, SQ3_MAXCOLS);
#endif
    sqlite3_busy_timeout(db, SQ3_BUSYTIMEOUT);
  }
  
  *sq3_hndl = (void *)db;
  return (retval);
}

int
db_sqlite3_close(void *sq3_hndl)
{
  int rc = -1;
  sqlite3 *db = NULL;

  if (sq3_hndl)
  {
    db = (sqlite3 *) sq3_hndl;
    if (db)
    {
      rc = sqlite3_close(db);
    }
  }
  return (rc);
}

int
db_sqlite3_parse_arg(char *arg)
{
  char tmp[DB_ARGLEN + 1];

  strncpy(tmp, arg, DB_ARGLEN);
  tmp[DB_ARGLEN] = '\0';
  realpath(tmp, arg);

  if ( access(arg, F_OK | R_OK | X_OK | W_OK) ) {
    W("Can't access database dir: %s (%s)", arg, strerror(errno));
    I("Trying to create %s", arg);
    if (mkdir(arg, S_IRUSR | S_IWUSR | S_IXUSR) != 0) {
      E("Create dir failed: %s", strerror(errno));
      return (DB_ERR_ARG);
    }
    return (0);
  }
  if ( !isdir(arg) )
    return (DB_ERR_ARG);

  return (0);
}

int
db_sqlite3_vexec_simple(void *sq3_hndl, const char *sql, va_list ap)
{
  const char *sql2exec = NULL;
  char *sqlbuf = NULL;
  char *errmsg;
  int ret = -1;

  assert(sq3_hndl != NULL);
  assert(sql != NULL);

#ifdef ENABLE_SQ3DBG
  DD("sqlite: %s", sql);
#endif
  if (strchr(sql, '%') != NULL) {
    sqlbuf = sqlite3_vmprintf(sql, ap);
    sql2exec = sqlbuf;
  } else {
    sql2exec = sql;
  }

  if (sqlite3_exec((sqlite3 *)sq3_hndl, sql2exec, NULL, NULL, &errmsg) != SQLITE_OK) {
    W("sqlite: %s", errmsg);
    sqlite3_free(errmsg);
    goto cleanup;
  }

  ret = 0;
cleanup:
  if (sqlbuf != NULL)
    sqlite3_free(sqlbuf);

  return (ret);
}

int
db_sqlite3_exec_simple(void *sq3_hndl, const char *sql, ...)
{
  int ret;
  va_list ap;

  va_start(ap, sql);
  ret = db_sqlite3_vexec_simple(sq3_hndl, sql, ap);
  va_end(ap);
  return (ret);
}

int
db_sqlite3_exec(void *sq3_hndl, void **db_stmt, db_result_t **result, const char *sql, const db_stmt_t *st, int len)
{
  bool first = true, second = true;
  int i, sqlret, coltype;
  db_result_t *col_vect = NULL, *row_vect = NULL;
  sqlite3_stmt *__st = NULL;
#ifdef ENABLE_SQ3DBG
  DD("%s", sql);
#endif
  if (db_stmt) __st = *db_stmt;
  if (!__st) {
    if ( (sqlret = sqlite3_prepare_v2(sq3_hndl, sql, SQ3_STMT_LEN, &__st, NULL) != SQLITE_OK) ) {
      E("sqlite: %s", sqlite3_errmsg(sq3_hndl));
      return (sqlret);
    }
  } else {
    sqlite3_reset(__st);
  }

  if (st) {
    for (i = 0; i < len; i++) {
      switch (st[i].dtype) {
        case db_uint:
        case db_int:
        case db_char:
          sqlite3_bind_int(__st, i+1, *((int *)st[i].bind_value));
          break;
        case db_str:
          sqlite3_bind_text(__st, i+1, (char *)st[i].bind_value, st[i].len, SQLITE_STATIC);
          break;
        case db_bin:
        case db_nulltype:
        default:
          break;
      }
    }
  }

  if (result) row_vect = *result = db_create_result();
  while( (sqlret = sqlite3_step(__st)) == SQLITE_ROW ) {
    if (!row_vect) break;
    if (!first) {
      row_vect->nextrow = db_create_result();
      row_vect = row_vect->nextrow;
    }
    col_vect = row_vect;
    second = true;

    for (i = 0; i < sqlite3_column_count(__st); i++) {
      if (!second) {
        col_vect->nextcol = db_create_result();
        col_vect = col_vect->nextcol;
      }

      switch ( (coltype = sqlite3_column_type(__st, i)) ) {
        case SQLITE_TEXT:
          col_vect->data = (void *)strdup((const char *)sqlite3_column_text(__st, i));
          break;
        case SQLITE_INTEGER:
          col_vect->data = calloc(1, sizeof(int));
          int nmbr = sqlite3_column_int(__st, i);
          memcpy((void *)col_vect->data, (void *)&nmbr, sizeof(int));
          break;
        case SQLITE_NULL:
          break;
        default:
          W("sqlite: unknown column type: %d", coltype);
          break;
      }
      second = false;
    }
    first = false;
  }
  switch (sqlret) {
    case SQLITE_ROW:
      sqlret = DB_ERR_MORE;
    case SQLITE_DONE:
      sqlret = DB_ERR_OK;
      break;
    default:
      E("sqlite: %s(%d)", sqlite3_errmsg(sq3_hndl), sqlret);
      break;
  }
  if (second) sqlret = DB_ERR_OK;

  if (!db_stmt) { 
    db_sqlite3_finish_stmt(__st);
  } else *db_stmt = __st;
  return (sqlret);
}

void
db_sqlite3_finish_stmt(void *dbstmt)
{
  sqlite3_stmt *dbst = dbstmt;
  if (dbst) sqlite3_finalize(dbst);
}
